package math;

import static math.ExpressionBuilder.not;
import static math.ExpressionBuilder.and;
import static math.ExpressionBuilder.or;

public class Not implements BooleanExpression {

  private BooleanExpression argument;
  
  private Not(BooleanExpression arg){
    this.argument = arg;
  }
  
  public static BooleanExpression createNot(BooleanExpression arg){
    return new Not(arg);
  }
  
  @Override
  public <R> R accept(ExpressionVisitor<R> visitor) {
    return visitor.visitNot(this);
  }

  @Override
  public int countMembers() {
    return 1 + argument.countMembers();
  }

  @Override
  public boolean matches(Expression expr) {
    if (expr instanceof Not) {
      Not eNot = (Not) expr;
      return argument.matches(eNot.getArgument());
    } else
      return false;
  }

  public BooleanExpression getArgument() {
    return argument;
  }

  @Override
  public Expression simplify() {
    return this;
  }
  
  @Override
  public String toString() {
    return "!(" + argument + ")";
  }
  
  @Override
  public boolean equals(Object obj) {
    if (obj instanceof Not) {
      Not not2 = (Not) obj;
      return not2.argument.equals(argument);
    } else
      return false;
  }

  @Override
  public int hashCode() {
    return argument.hashCode() ^ 347892478;
  }

  @Override
  public BooleanExpression toDNF() {
    if (argument instanceof BooleanVariable)
      return this;
    if (argument instanceof BooleanConstant)
      return ((BooleanConstant)argument).negate();
    if (argument instanceof Not)
      return ((Not)argument).argument.toDNF();
    if (argument instanceof And){
      And and = (And) argument;
      return or(not(and.getArg1()), not(and.getArg2())).toDNF();
    }
    if (argument instanceof Or){
      Or or = (Or) argument;
      return and(not(or.getArg1()), not(or.getArg2())).toDNF();
    }
    if (argument instanceof BooleanEquality){
      BooleanEquality beq = (BooleanEquality) argument;
      return or(and(beq.getArg1(), not(beq.getArg2())), and(not(beq.getArg1()), beq.getArg2())).toDNF();
    }
    if (argument instanceof Xor){
      Xor xor = (Xor) argument;
      return or(and(xor.getArg1(), xor.getArg2()), and(not(xor.getArg1()), not(xor.getArg2()))).toDNF();
    }
    BooleanExpression newArg = argument.toDNF();
    if (newArg != argument)
      return not(newArg).toDNF();
    return this;
  }

}